package web

import (
	"fmt"
	"log"
	"regexp"
	"strings"
)

type router struct {
	// trees 是按照 HTTP 方法来组织的
	// 如 GET => *node
	trees map[string]*node
}

func newRouter() router {
	return router{
		trees: map[string]*node{},
	}
}
func (r *router) addRoute(method string, path string, handler HandleFunc) {
	// 这里注册到路由树里面
	if path == "" {
		panic("web: 路由是空字符串")
	}
	// login/
	if path[0] != '/' {
		panic("web: 路由必须以 / 开头")
	}
	//  /login/////
	if path != "/" && path[len(path)-1] == '/' {
		panic("web: 路由不能以 / 结尾")
	}

	root, ok := r.trees[method]
	if !ok {
		// 创建根节点
		root = &node{path: "/"}
		r.trees[method] = root
	}

	if path == "/" {
		if root.handler != nil {
			panic("web: 路由冲突[/]")
		}
		root.handler = handler
		return
	}

	segs := strings.Split(path[1:], "/")
	// 开始一段段处理
	for _, s := range segs {
		if s == "" {
			panic(fmt.Sprintf("web: 非法路由。不允许使用 //a/b, /a//b 之类的路由, [%s]", path))
		}
		root = root.childOrCreate(s)
	}
	if root.handler != nil {
		panic(fmt.Sprintf("web: 路由冲突[%s]", path))
	}

	root.handler = handler
}

type nodeType int

// 通配符路由
const nodeTypeStar = 1

// 正则路由
const nodeTypeReg = 2

// 参数路由
const nodeTypeParam = 3

// node 代表路由树的节点
// 路由树的匹配顺序是：
// 1. 静态完全匹配
// 2. 通配符匹配
// 这是不回溯匹配
type node struct {
	nType nodeType
	path  string
	// children 子节点
	// 子节点的 path => node
	children map[string]*node
	// handler 命中路由之后执行的逻辑
	handler HandleFunc

	// 通配符 * 表达的节点，任意匹配
	startChild *node

	paramChild *node

	// 正则路由和参数路由都会使用这个字段
	paramName string

	// 正则表达式
	regChild *node
	regExpr  *regexp.Regexp
}

// childOrCreate 查找子节点，如果子节点不存在就创建一个
// 并且将子节点放回去了 children 中
func (n *node) childOrCreate(path string) *node {
	if path == "*" {
		if n.paramChild != nil {
			panic(fmt.Sprintf("web: 非法路由，已有路径参数路由。不允许同时注册通配符路由和参数路由 [%s]", path))
		}
		if n.startChild == nil {
			n.startChild = &node{path: "*", nType: nodeTypeStar}
		}
		return n.startChild
	}

	// 以 : 开头，我们认为是参数路由， 进一步解析，判断是不是正则路由
	if path[0] == ':' {
		paramName, expr, isReg := n.parseParam(path)
		if isReg {
			if n.startChild != nil {
				// TODO test
				panic(fmt.Sprintf("web: 非法路由，已有通配符路由。不允许同时注册通配符路由、正则路由和参数路由 [%s]", path))
			}
			if n.paramChild != nil {
				// TODO test
				panic(fmt.Sprintf("web: 非法路由，已有路径参数路由。不允许同时注册正则路由和参数路由 [%s]", path))
			}
			if n.regChild != nil {
				if n.regChild.regExpr.String() != expr || n.paramName != paramName {
					// TODO test
					panic(fmt.Sprintf("web: 路由冲突，正则路由冲突，已有 %s，新注册 %s", n.regChild.path, path))
				}
			} else {
				// 注册 正则路由
				regExpr, err := regexp.Compile(expr)
				if err != nil {
					// TODO test
					panic(fmt.Errorf("web: 正则表达式错误 %w", err))
				}
				n.regChild = &node{
					path:      path,
					paramName: paramName,
					regExpr:   regExpr,
					nType:     nodeTypeReg,
				}
			}
			return n.regChild
		}

		if n.regChild != nil {
			panic(fmt.Sprintf("web: 非法路由，已有正则路由。不允许同时注册正则路由和参数路由 [%s]", path))
		}
		if n.startChild != nil {
			panic(fmt.Sprintf("web: 非法路由，已有通配符路由。不允许同时注册通配符路由和参数路由 [%s]", path))
		}
		if n.paramChild != nil {
			if n.paramChild.path != path {
				panic(fmt.Sprintf("web: 路由冲突，参数路由冲突，已有 %s，新注册 %s", n.paramChild.path, path))
			}
		} else {
			n.paramChild = &node{path: path, paramName: paramName}
			//n.paramChild = &node{path: path, paramName: paramName, nType: nodeTypeParam}
		}
		return n.paramChild

	}

	if n.children == nil {
		n.children = make(map[string]*node)
	}
	child, ok := n.children[path]
	if !ok {
		child = &node{path: path}
		n.children[path] = child
	}

	return child

}

// child 返回子节点
// 第一个返回值 *node 是命中的节点
// 第二个返回值 bool 代表是否是命中参数路由
// 第三个返回值 bool 代表是否命中
func (n *node) childOf(path string) (*node, bool, bool) {
	if n.children == nil {
		if n.regChild != nil {
			if n.regChild.regExpr.Match([]byte(path)) {
				// TODO 现在的匹配逻辑，只要匹配成功就可以，但是如果正则是只匹配数字， 那么像 "111r"，"2222R444"是不是应该不要匹配成功
				// 解决思路：可以把匹配处理的字符串做对比，可以判断是不是完整匹配成功了
				// TODO * 的情况能不能完全是 正则的一种情况呢？
				log.Printf("reg Match: %v", n.regChild.regExpr.Match([]byte(path)))
				return n.regChild, true, true
			}
		}
		if n.paramChild != nil {
			return n.paramChild, true, true
		}
		//return nil, false
		return n.startChild, false, n.startChild != nil
	}
	res, ok := n.children[path]
	if !ok {
		// TODO 正则和参数验证的代码封装
		if n.regChild != nil {
			if n.regChild.regExpr.Match([]byte(path)) {
				log.Printf("reg Match: %v", n.regChild.regExpr.Match([]byte(path)))
				return n.regChild, true, true
			}
		}
		if n.paramChild != nil {
			return n.paramChild, true, true
		}
		return n.startChild, false, n.startChild != nil
	}
	return res, false, ok
}

// parseParam 用于解析判断是不是正则表达式
// 第一个返回值是参数名字
// 第二个返回值是正则表达式
// 第三个返回值为 true 则说明是正则路由
func (n *node) parseParam(path string) (string, string, bool) {
	// 去除：
	path = path[1:]
	segs := strings.SplitN(path, "(", 2)
	if len(segs) == 2 {
		expr := segs[1]
		if strings.HasSuffix(expr, ")") {
			return segs[0], expr[:len(expr)-1], true
		}
	}
	return path, "", false
}

// findRoute 查找对应的节点
// 注意，返回的 node 内部 HandleFunc 不为 nil 才算是注册了路由
func (r router) findRoute(method string, path string) (*matchInfo, bool) {
	root, ok := r.trees[method]
	if !ok {
		return nil, false
	}
	if path == "/" {
		return &matchInfo{n: root}, true
	}
	segs := strings.Split(strings.Trim(path, "/"), "/")
	mi := &matchInfo{}
	for _, s := range segs {
		var matchParam bool
		var child *node
		child, matchParam, ok = root.childOf(s)
		if !ok {
			// 匹配 最后的 *
			if root.nType == nodeTypeStar {
				mi.n = root
				return mi, true
			}
			return nil, false
		}
		root = child
		if matchParam {
			mi.addValue(root.paramName, s)
		}
	}
	mi.n = root
	return mi, true
}

type matchInfo struct {
	n          *node
	pathParams map[string]string
}

func (m *matchInfo) addValue(key string, value string) {
	if m.pathParams == nil {
		m.pathParams = map[string]string{key: value}
	}
	m.pathParams[key] = value
}
